package edu.mercer.sse.database;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

import edu.mercer.sse.errors.CustomLogger;
import edu.mercer.sse.errors.LoggerCustom;
import edu.mercer.sse.app.Config;

/**
 * 
 * Rows retrieved from database according to Database -> LDAP layout.<p>
 * 
 * @see DB2LDAPLayout
 *
 */

public class DBRecordSet
{
	/* connection, layout expected */
	
	private static DBConnector _database = new DBConnector();

	/* data retrieved */
	
	private static List<ImmutableMap<String,String>> _dbData = Lists
			.newArrayList();
	
	private static List<String> _dbColumns = Lists.newArrayList();	
	
	/* logging */
	
	private static Boolean _verbose = Config.settings()
			 						 		.getProperty("verbose")
			 						 		.equalsIgnoreCase("true");
	
	private static final CustomLogger _log = LoggerCustom.obtainLog( 
			"DBRecordSet", Config.getLogLevel());

	/**
	 * DBRecordSet constructor.<p>
	 * 
	 * Uses own database connector instance.<p>
	 * 
	 * @throws SQLException
	 * 
	 * @see DB2LDAPLayout
	 * 
	 * @see {@link #DBRecordSet(DBConnector)}
	 */
	
	public DBRecordSet() throws SQLException
	{
		checkArgument( DB2LDAPLayout.getLayout().size() > 0, 
				"empty BiMap: ldap layout required" );

		init();
		
	}//constructor


	/**
	 * DBRecordSet constructor.<p>
	 * 
	 * Allows specification of database connector for re-usage.<p>
	 * 
	 * @param databaseConnector - DBConnector
	 * 
	 * @throws SQLException
	 * 
	 * @see DB2LDAPLayout
	 * 
	 * @see {@link #DBRecordSet(DB2LDAPLayout)}
	 */
	
	public DBRecordSet( final DBConnector databaseConnector ) 
	
						throws SQLException
	{
		withDBConnector( databaseConnector );
		
		checkArgument( DB2LDAPLayout.getLayout().size() > 0, 
				"empty BiMap: ldap layout required" );

		_database = databaseConnector;
		
		init();
		
	}//constructor

	
	public static void refresh() throws SQLException
	{
		_dbData.clear();
		_dbColumns.clear();
		
	}//method
	
	
	public static void withDBConnector( final DBConnector dbconn )
	{
		checkNotNull( dbconn, "null DBConnector: DBConnector instance" 
				+ " required" );
		
		_database = dbconn;
		
	}//method


	public static void download() throws SQLException
	{
		getDBData();
		
	}//method
	
	
	private static void init() throws SQLException
	{
		getDBData();
		
	}//method
	
	
	private static void getDBData() throws SQLException
	{
		refresh();
		
		final Statement entriesQryState = _database.con().createStatement();
		
		entriesQryState.executeQuery("SELECT * FROM `"	
				
				+ Config.settings().getProperty(
						"database-ldap-view") 
				
				+ "` LIMIT " 
				
				+ Config.settings().getProperty(
						"database-ldap-view-limit"));
		
		final ResultSet entries = entriesQryState.getResultSet();
		
		//get column names
		
		//column indexes start from 1
		for( Integer i = 1; 
					 i < entries.getMetaData()
					 			.getColumnCount() + 1; 
					 i++) 
		{
			_dbColumns.add( entries.getMetaData()
		    				       .getColumnName(i) );
		    
		}//for
		
		while( entries.next() )
		{
			//iterate ldapLayout keys which correspond to qry columns
			
			final Map<String,String> dbColumnsMap = new HashMap<>();
			
			for( String colName : DB2LDAPLayout.getLayout().keySet() )
			{
				if( _dbColumns.contains(colName) )
				{
					dbColumnsMap.put( colName, entries.getString(colName) );

					if( _verbose ) _log.fine( colName + " == " + entries
							.getString(colName) ); 
				
				}//if
				
				//add any literals to ldap specified in layout
				
				else if( colName.matches("'[a-zA-Z0-9_\\-]{1,}'") )
				{
					//gidNumber for ldap, literal from layout/database = '100'
					
					dbColumnsMap.put( DB2LDAPLayout.getLayout().get(colName), 
							colName );

					if( _verbose )
					{ 
						_log.fine( DB2LDAPLayout.getLayout().get(colName) 
							+ " == " + colName ); 
					
					}//if
					
				}//if
				
			}//for
			
			_dbData.add( ImmutableMap.copyOf( dbColumnsMap ) );
			
		}//while
				
		
		if( _verbose ) _log.fine( "Entries from db: " + _dbData.size() ); 
				
	}//method

	
	/**
	 * Get database data from query.<p>
	 * 
	 * Retrieves rows from database query as specified in database->LDAP 
	 * 	layout.<p>
	 * 
	 * @return  ImmutableList&#60;ImmutableMap&#60;String,String&#62;&#62; of
	 * 	rows from database query
	 * 
	 * @throws SQLException  database query, connectivity or other error
	 * 
	 * @see DB2LDAPLayout
	 */
	
	public synchronized static ImmutableList<ImmutableMap<String,String>> 
			getData() throws SQLException
	{
		if( _dbData.size() < 1 ) getDBData();
		
		return ImmutableList.copyOf( _dbData );
		
	}//method


	/**
	 * Get database column headings from query.<p>
	 * 
	 * Retrieves column headings from rows of database query as specified in 
	 * 	database->LDAP layout.<p>
	 * 
	 * @return  ImmutableList&#60;String&#62; of column headings from rows of 
	 * 	database query
	 * 
	 * @throws SQLException  database query, connectivity or other error
	 * 
	 * @see DB2LDAPLayout
	 */
	
	public synchronized static ImmutableList<String> getColumns() 
			throws SQLException
	{
		if( _dbData.size() < 1 ) getDBData();
		
		return ImmutableList.copyOf( _dbColumns );
		
	}//method
		
	
}//class